#include "SceneHierarchyPanel.h"

#include "Core/GlobalContext.h"

#include <ImGuiFileDialog.h>

namespace SE
{
	template<typename _Ty, typename UIFunction>
	static void DrawComponent(const String& name, Entity entity, UIFunction uiFunction)
	{
		const ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags_DefaultOpen |
												 ImGuiTreeNodeFlags_Framed |
												 ImGuiTreeNodeFlags_SpanAvailWidth |
												 ImGuiTreeNodeFlags_AllowItemOverlap |
												 ImGuiTreeNodeFlags_FramePadding;
		if (entity.hasComponent<_Ty>())
		{
			auto& component = entity.getComponent<_Ty>();
			ImVec2 contentRegionAvailable = ImGui::GetContentRegionAvail();

			ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{ 4, 4 });
			Float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f;
			ImGui::Separator();
			Boolean open = ImGui::TreeNodeEx((void*)typeid(_Ty).hash_code(), treeNodeFlags, name.data());
			ImGui::PopStyleVar(
			);
			ImGui::SameLine(contentRegionAvailable.x - lineHeight * 0.5f);
			if (ImGui::Button("+", ImVec2{ lineHeight, lineHeight }))
			{
				ImGui::OpenPopup("ComponentSettings");
			}

			Boolean removeComponent = false;
			if (ImGui::BeginPopup("ComponentSettings"))
			{
				if (ImGui::MenuItem("Remove component"))
					removeComponent = true;

				ImGui::EndPopup();
			}

			if (open)
			{
				uiFunction(component);
				ImGui::TreePop();
			}

			if (removeComponent)
				entity.removeComponent<_Ty>();
		}
	}

	static void DrawVec3Control(const String& label, Vector3f& values, const Float resetValue = 0.0f, const Float columnWidth = 100.0f)
	{
		ImGuiIO& io = ImGui::GetIO();
		auto boldFont = io.Fonts->Fonts[0];

		ImGui::PushID(label.data());

		ImGui::Columns(2);
		ImGui::SetColumnWidth(0, columnWidth);
		ImGui::Text(label.data());
		ImGui::NextColumn();

		ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
		ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 });

		Float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f;
		ImVec2 buttonSize = { lineHeight + 3.0f, lineHeight };

		ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.9f, 0.2f, 0.2f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f });
		ImGui::PushFont(boldFont);
		if (ImGui::Button("X", buttonSize))
			values.x = resetValue;
		ImGui::PopFont();
		ImGui::PopStyleColor(3);

		ImGui::SameLine();
		ImGui::DragFloat("##X", &values.x, 0.1f, 0.0f, 0.0f, "%.2f");
		ImGui::PopItemWidth();
		ImGui::SameLine();

		ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.2f, 0.7f, 0.2f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.3f, 0.8f, 0.3f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.2f, 0.7f, 0.2f, 1.0f });
		ImGui::PushFont(boldFont);
		if (ImGui::Button("Y", buttonSize))
			values.y = resetValue;
		ImGui::PopFont();
		ImGui::PopStyleColor(3);

		ImGui::SameLine();
		ImGui::DragFloat("##Y", &values.y, 0.1f, 0.0f, 0.0f, "%.2f");
		ImGui::PopItemWidth();
		ImGui::SameLine();

		ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.1f, 0.25f, 0.8f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.2f, 0.35f, 0.9f, 1.0f });
		ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.1f, 0.25f, 0.8f, 1.0f });
		ImGui::PushFont(boldFont);
		if (ImGui::Button("Z", buttonSize))
			values.z = resetValue;
		ImGui::PopFont();
		ImGui::PopStyleColor(3);

		ImGui::SameLine();
		ImGui::DragFloat("##Z", &values.z, 0.1f, 0.0f, 0.0f, "%.2f");
		ImGui::PopItemWidth();

		ImGui::PopStyleVar();

		ImGui::Columns(1);

		ImGui::PopID();
	}

	void SceneHierarchyPanel::setCurrentScene(const Ref<Scene>& scene)
	{
		mCurrentScene = scene;
	}

	void SceneHierarchyPanel::onRender(const Double deltaMilliseconds)
	{
		ImGui::Begin("Scene Hierarchy Panel");
		if (!mCurrentScene.expired())
		{
			auto currentScene = mCurrentScene.lock();

			currentScene->mRegistry.each([&](auto entityID) {
				Entity entity = { currentScene.get(), entityID };
				drawEntityNode(entity);
			});

			if (ImGui::IsMouseDown(0) && ImGui::IsWindowHovered())
				mSelectedEntity = {};

			// Right-click on blank space
			if (ImGui::BeginPopupContextWindow(0, ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems))
			{
				if (ImGui::MenuItem("Create Empty Entity"))
					currentScene->createEntity("Empty Entity");

				ImGui::EndPopup();
			}

			auto assetsPath = GlobalContext::GetConfigManager()->getAssetsDir();
			static String bezierSurfacePath;
			
			if (ImGui::Button("Load Bezier Surface"))
			{
				ImGuiFileDialog::Instance()->OpenDialog("Choose Mesh File", "Choose File", ".bsm", assetsPath, 1, nullptr, ImGuiFileDialogFlags_Modal);
			}
			if (ImGuiFileDialog::Instance()->Display("Choose Mesh File"))
			{
				if (ImGuiFileDialog::Instance()->IsOk())
				{
					auto path = ImGuiFileDialog::Instance()->GetFilePathName();
					if (path != bezierSurfacePath)
					{
						bezierSurfacePath = path;

						std::ifstream inFile(path);
						if (!inFile.is_open())
						{
							std::cerr << "Error opening file." << std::endl;
							return;
						}

						while (!inFile.eof())
						{
							String line;
							std::getline(inFile, line);

							if (line.empty())
							{
								break;
							}

							std::istringstream iss(line);

							auto entity = currentScene->createEntity("Bezier");
							auto bezierSurface = std::dynamic_pointer_cast<BezierSurface>(entity.addComponent<MeshRendererComponent>().meshInstance);
							auto& transform = entity.getComponent<TransformComponent>();

							for (I32 i = 0; i < 4; ++i)
							{
								for (I32 j = 0; j < 4; ++j)
								{
									iss >> bezierSurface->mControlPoints[i][j].x
										>> bezierSurface->mControlPoints[i][j].y
										>> bezierSurface->mControlPoints[i][j].z;
								}
							}

							iss >> transform.position.x >> transform.position.y >> transform.position.z
								>> transform.rotation.x >> transform.rotation.y >> transform.rotation.z
								>> transform.scale.x >> transform.scale.y >> transform.scale.z;
						}

						inFile.close();
					}
				}
				ImGuiFileDialog::Instance()->Close();
			}

			static char saveName[256] = { 0 };
			ImGui::InputText("##Tag", saveName, sizeof(saveName));
			ImGui::SameLine();
			ImGui::Text(".bsm");
			if (ImGui::Button("Save Bezier Surface"))
			{
				// Load bezier surface meshes.
				String strName = String(saveName);
				std::ofstream outFile(assetsPath + "/models/" + strName + ".bsm");
				if (!outFile.is_open())
				{
					std::cerr << "Error opening file." << std::endl;
					return;
				}

				auto view = currentScene->mRegistry.view<TransformComponent, MeshRendererComponent>();
				for (auto entity : view)
				{
					auto [transformComponent, meshRendererComponent] = view.get<TransformComponent, MeshRendererComponent>(entity);

					if (auto bezierSurface = std::dynamic_pointer_cast<BezierSurface>(meshRendererComponent.meshInstance))
					{
						for (I32 i = 0; i < 4; ++i)
						{
							for (I32 j = 0; j < 4; ++j)
							{
								outFile << bezierSurface->mControlPoints[i][j].x << " "
										<< bezierSurface->mControlPoints[i][j].y << " "
										<< bezierSurface->mControlPoints[i][j].z << " ";
							}
						}

						outFile << transformComponent.position.x << " " << transformComponent.position.y << " " << transformComponent.position.z << " "
								<< transformComponent.rotation.x << " " << transformComponent.rotation.y << " " << transformComponent.rotation.z << " "
								<< transformComponent.scale.x << " " << transformComponent.scale.y << " " << transformComponent.scale.z << "\n";
					}
				}
				outFile.close();
			}
		}
		ImGui::End();

		ImGui::Begin("Properties");
		if (mSelectedEntity)
		{
			drawComponents(mSelectedEntity);
		}

		ImGui::End();
	}

	String SceneHierarchyPanel::getName() const
	{
		return "Scene Hierarchy Panel";
	}

	void SceneHierarchyPanel::drawEntityNode(Entity entity)
	{
		if (!mCurrentScene.expired())
		{
			auto currentScene = mCurrentScene.lock();

			auto& tag = entity.getComponent<TagComponent>().tag;

			ImGuiTreeNodeFlags flags = ((mSelectedEntity == entity) ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_OpenOnArrow;
			flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
			Boolean opened = ImGui::TreeNodeEx((void*)(U64)(U32)entity, flags, tag.data());
			if (ImGui::IsItemClicked())
			{
				mSelectedEntity = entity;
			}

			Boolean entityDeleted = false;
			if (ImGui::BeginPopupContextItem())
			{
				if (ImGui::MenuItem("Delete Entity"))
					entityDeleted = true;

				ImGui::EndPopup();
			}

			if (opened)
			{
				ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
				Boolean opened = ImGui::TreeNodeEx((void*)9817239, flags, tag.data());
				if (opened)
					ImGui::TreePop();
				ImGui::TreePop();
			}

			if (entityDeleted)
			{
				currentScene->destroyEntity(entity);
				if (mSelectedEntity == entity)
					mSelectedEntity = {};
			}
		}
	}

	void SceneHierarchyPanel::drawComponents(Entity entity)
	{
		if (entity.hasComponent<TagComponent>())
		{
			auto& tag = entity.getComponent<TagComponent>().tag;

			char buffer[256];
			memset(buffer, 0, sizeof(buffer));
			strncpy_s(buffer, sizeof(buffer), tag.data(), sizeof(buffer));
			if (ImGui::InputText("##Tag", buffer, sizeof(buffer)))
			{
				tag = String(buffer);
			}
		}

		ImGui::SameLine();
		ImGui::PushItemWidth(-1);

		if (ImGui::Button("Add Component"))
			ImGui::OpenPopup("AddComponent");

		if (ImGui::BeginPopup("AddComponent"))
		{
			displayAddComponentEntry<TransformComponent>("Transform");
			displayAddComponentEntry<CameraComponent>("Camera");
			displayAddComponentEntry<ScriptableComponent>("Script");
			displayAddComponentEntry<MeshRendererComponent>("Mesh Renderer");

			ImGui::EndPopup();
		}

		ImGui::PopItemWidth();

		DrawComponent<TransformComponent>("Transform", entity, [](TransformComponent& transformComponent)
		{
			DrawVec3Control("Translation", transformComponent.position);
			Vector3f rotation = glm::degrees(transformComponent.rotation);
			DrawVec3Control("Rotation", rotation);
			transformComponent.rotation = glm::radians(rotation);
			DrawVec3Control("Scale", transformComponent.scale, 1.0f);
		});

		DrawComponent<CameraComponent>("Camera", entity, [](CameraComponent& cameraComponent)
		{
			ImGui::Checkbox("Primary", &cameraComponent.actived);

			const char* projectionTypeStrings[] = { "Perspective", "Orthographic" };
			const char* currentProjectionTypeString = projectionTypeStrings[I32(cameraComponent.type)];
			if (ImGui::BeginCombo("Projection", currentProjectionTypeString))
			{
				for (I32 i = 0; i < 2; i++)
				{
					Boolean isSelected = currentProjectionTypeString == projectionTypeStrings[i];
					if (ImGui::Selectable(projectionTypeStrings[i], isSelected))
					{
						currentProjectionTypeString = projectionTypeStrings[i];
						cameraComponent.type = CameraType(i);
					}

					if (isSelected)
						ImGui::SetItemDefaultFocus();
				}

				ImGui::EndCombo();
			}

			if (CameraType::Perspective == cameraComponent.type)
			{
				static Float perspectiveVerticalFov = cameraComponent.perspectiveFOV;
				static Float perspectiveNear = cameraComponent.perspectiveNear;
				static Float perspectiveFar = cameraComponent.perspectiveFar;
				if (ImGui::DragFloat("Vertical FOV", &perspectiveVerticalFov))
				{
					cameraComponent.perspectiveFOV = perspectiveVerticalFov;
				}
				if (ImGui::DragFloat("Near", &perspectiveNear))
				{
					cameraComponent.perspectiveNear = perspectiveNear;
				}
				if (ImGui::DragFloat("Far", &perspectiveFar))
				{
					cameraComponent.perspectiveFar = perspectiveFar;
				}
			}

			if (CameraType::Orthographic == cameraComponent.type)
			{
				static Float orthographicSize = glm::degrees(cameraComponent.orthographicSize);
				static Float orthographicNear = cameraComponent.orthographicNear;
				static Float orthographicFar = cameraComponent.orthographicFar;
				if (ImGui::DragFloat("Size", &orthographicSize))
				{
					cameraComponent.orthographicSize = glm::radians(orthographicSize);
				}
				if (ImGui::DragFloat("Near", &orthographicNear))
				{
					cameraComponent.orthographicNear = orthographicNear;
				}
				if (ImGui::DragFloat("Far", &orthographicFar))
				{
					cameraComponent.orthographicFar = orthographicFar;
				}
			}
		});

		DrawComponent<MeshRendererComponent>("Mesh Renderer", entity, [](MeshRendererComponent& meshRendererComponent)
		{
			if (auto bezierSurface = std::dynamic_pointer_cast<BezierSurface>(meshRendererComponent.meshInstance))
			{
				static I32 x, y;
				for (I32 i = 0; i < 4; ++i)
				{
					for (I32 j = 0; j < 4; ++j)
					{
						StringStream ss;
						ss << 'p' << i << j;
						auto buttonName = ss.str();
						ImGui::PushID(buttonName.data());
						if (ImGui::Button(buttonName.data()))
						{
							x = i;
							y = j;
						}
						if (j != 3)
						{
							ImGui::SameLine();
						}

						ImGui::PopID();
					}
				}
				if (x >= 0 && x <= 4 && y >= 0 && y <= 4)
				{
					ImGui::Text("Current: %d, %d", x, y);
					DrawVec3Control("Position", bezierSurface->mControlPoints[x][y]);
				}
			}
		});
	}

	template<typename _Ty>
	void SceneHierarchyPanel::displayAddComponentEntry(const String& entryName)
	{
		if (!mSelectedEntity.hasComponent<_Ty>())
		{
			if (ImGui::MenuItem(entryName.data()))
			{
				mSelectedEntity.addComponent<_Ty>();
				ImGui::CloseCurrentPopup();
			}
		}
	}
} // !namespace SE